/*
 * MIT License
 *
 * Copyright (c) 2020 wen.gu <454727014@qq.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

 /***************************************************************************
 * Name: watchdog.cpp
 *
 * Purpose: watchdog implementation.
 *
 * Developer:
 *   wen.gu , 2021-04-02
 *
 * TODO:
 *
 ***************************************************************************/

 /******************************************************************************
 **    INCLUDES
 ******************************************************************************/
#include "icpp/executor/watchdog.h"
#include <mutex>
#include <condition_variable>
#include <chrono>

#include "icpp/executor/thread.h"
#include "icpp/core/utils.h"


namespace icpp
{
namespace executor
{
/******************************************************************************
 **    MACROS
 ******************************************************************************/

/******************************************************************************
 **    VARIABLE DEFINITIONS
 ******************************************************************************/

class Watchdog::Impl
{
public:    
    bool is_enabled_ = false;
    bool is_running_ = false;
    bool is_idle_ = false;
    bool cancel_wait_ = false;

    Thread* thd_ = nullptr;
    int64_t base_time_ = 0;
    int64_t counter_ = 0;
    BarkHandler handler_ =nullptr;
    BarkHandler trigger_handler_ = nullptr;
    std::mutex lock_;
    std::condition_variable cond_var_;
public:
    int64_t nowCounter()
    {
        return core::NowSteadyMs() - base_time_ ;
    }

    bool initHandler(BarkHandler handler)
    {
        handler_ = handler;
        if (thd_ == nullptr)
        {
            thd_ = new Thread("watchdog", [this]()-> bool
            {
                while (is_running_)
                {
                    if (is_idle_)
                    {
                        core::AutoLock al(lock_);
                        cond_var_.wait(al, [this]() -> bool {return is_idle_;});
                    }

                    int64_t wait_time = counter_ - nowCounter();
                    if (wait_time > 0)
                    {
                        core::AutoLock al(lock_);
                        if (!cancel_wait_ && cond_var_.wait_for(al, std::chrono::milliseconds(wait_time)) != std::cv_status::timeout)
                        {
                            /** maybe dog be feed by someone */
                            continue;
                        }
                    }

                    if (trigger_handler_)
                    {
                        trigger_handler_();
                        core::AutoLock al(lock_);
                        is_idle_ = true;
                    }
                }
                
                return false;
            });
        }  

        return true;     
    }

    bool stopHandler()
    {
        if (thd_)
        {
            {
                core::AutoLock al(lock_);
                is_running_ = false;
                is_idle_ = false;  
                cancel_wait_ = true;
                trigger_handler_ = nullptr;
                cond_var_.notify_all();              
            }

            thd_->join();

            delete thd_;
            thd_ = nullptr;
        }

        return true;
    }
};

/******************************************************************************
 **    FUNCTION DEFINITIONS
 ******************************************************************************/
Watchdog::Watchdog()
    :impl_(new Impl)
{
    /** todo something */
}

Watchdog::~Watchdog()
{
    /** todo something */
    impl_->stopHandler();
}

/**
 * counter_ms: the time of watchdog timeout
 */
bool Watchdog::enable(uint32_t counter_ms) /** when this funcation called, watchdog start to counter */
{
    if (counter_ms == 0)
    {
        return false;
    }

    core::AutoLock al(impl_->lock_);
    impl_->counter_ = counter_ms;
    impl_->base_time_ = core::NowSteadyMs();
    impl_->is_enabled_ = true;

    if (impl_->thd_)
    {
        impl_->is_running_ = true;
        impl_->is_idle_ = false;
        impl_->cancel_wait_ = false;
        impl_->trigger_handler_ = impl_->handler_;
        impl_->thd_->start();
    }

    return true;
}

bool Watchdog::disable()
{
    core::AutoLock al(impl_->lock_);
    impl_->is_enabled_ = false;
    if (impl_->thd_)
    {
        impl_->is_idle_ = true;
        impl_->cancel_wait_ = true;
        impl_->trigger_handler_ = nullptr;
        impl_->cond_var_.notify_all();
    }
}

bool Watchdog::feedDog()
{
    if (impl_->is_enabled_)
    {
        core::AutoLock al(impl_->lock_);
        impl_->base_time_ = core::NowSteadyMs();

        if (impl_->thd_)
        {
            impl_->is_idle_ = false;
            impl_->cancel_wait_ = false;        
            impl_->cond_var_.notify_all();    
        }

        return true;
    }
    
    return false;
}

bool Watchdog::bark() /** is watchdog timeout */
{
    core::AutoLock al(impl_->lock_);
    if (impl_->is_enabled_)
    {
        return (impl_->nowCounter() >= impl_->counter_);
    }
    
    return false;
}

/**
 * Sets the bark handler. Will only be effective before enable() is called.
 */
void Watchdog::setBarkHandler(BarkHandler handler)
{
    if (handler != nullptr)
    {
        impl_->initHandler(handler);
    }
}

} /** namespace executor */
} /** namespace icpp */


